<?php
namespace Net\Curl;

use \Base\UnitTest\RunkitTestCase as RunkitTestCase;

require_once __DIR__ . '/RunkitTestCase.php';
require_once __DIR__ . '/../Curl.php';

class CurlTest
    extends RunkitTestCase
{
    protected $mock;

    protected function setUp()
    {
        $this->mock = $this->runkitMockFunctions(array(
            'curl_init',
            'curl_close',
            'curl_errno',
            'curl_error',
            'curl_getinfo',
            'curl_setopt',
            'curl_setopt_array',
            'curl_version',
            'curl_copy_handle',
            'curl_exec',
        ));
    }

    protected function tearDown()
    {
        $this->runkitRevertAll();
    }

    /**
     * testCtorGetHandleDtor
     *
     * @return void
     */
    public function testCtorGetHandleDtor()
    {
        $this->mock
            ->expects($this->at(0))
            ->method('curl_init')
            ->with(null)
            ->will($this->returnValue('my handle'));

        $this->mock
            ->expects($this->at(1))
            ->method('curl_close')
            ->with('my handle');

        $c = new Curl();
        $this->assertEquals('my handle', $c->getHandle());
    }

    public function testClone()
    {
        $this->mock
            ->expects($this->at(0))
            ->method('curl_init')
            ->with(null)
            ->will($this->returnValue('my handle'));

        $this->mock
            ->expects($this->at(1))
            ->method('curl_copy_handle')
            ->with('my handle')
            ->will($this->returnValue('my clone'));

        $c = new Curl();
        $clone = clone($c);
        $this->assertEquals('my clone', $clone->getHandle());
    }

    /**
     * testCtorParam
     *
     * @return void
     */
    public function testCtorParam()
    {
        $this->mock
            ->expects($this->once())
            ->method('curl_init')
            ->with('my url');

        $c = new Curl('my url');
    }

    /**
     * testErrno
     *
     * @return void
     */
    public function testErrno()
    {
        $this->mock
            ->expects($this->at(0))
            ->method('curl_init')
            ->with(null)
            ->will($this->returnValue('my handle'));

        $this->mock
            ->expects($this->once())
            ->method('curl_errno')
            ->with('my handle')
            ->will($this->returnValue(42));

        $c = new Curl();
        $this->assertEquals(42, $c->errno());
    }

    /**
     * testError
     *
     * @return void
     */
    public function testError()
    {
        $this->mock
            ->expects($this->at(0))
            ->method('curl_init')
            ->with(null)
            ->will($this->returnValue('my handle'));

        $this->mock
            ->expects($this->once())
            ->method('curl_error')
            ->with('my handle')
            ->will($this->returnValue('my err'));

        $c = new Curl();
        $this->assertEquals('my err', $c->error());
    }

    /**
     * testGetInfo
     *
     * @return void
     */
    public function testGetInfo()
    {
        $this->mock
            ->expects($this->at(0))
            ->method('curl_init')
            ->with(null)
            ->will($this->returnValue('my handle'));

        $this->mock
            ->expects($this->at(1))
            ->method('curl_getinfo')
            ->with('my handle', 0)
            ->will($this->returnValue('my info 1'));

        $this->mock
            ->expects($this->at(2))
            ->method('curl_getinfo')
            ->with('my handle', 33)
            ->will($this->returnValue('my info 2'));

        $c = new Curl();
        $this->assertEquals('my info 1', $c->getInfo());
        $this->assertEquals('my info 2', $c->getInfo(33));
    }

    /**
     * testSetOpt
     *
     * @return void
     */
    public function testSetOpt()
    {
        $this->mock
            ->expects($this->at(0))
            ->method('curl_init')
            ->with(null)
            ->will($this->returnValue('my handle'));

        $this->mock
            ->expects($this->at(1))
            ->method('curl_setopt')
            ->with('my handle', 'my opt', 'my val')
            ->will($this->returnValue(true));

        $this->mock
            ->expects($this->at(2))
            ->method('curl_setopt_array')
            ->with('my handle', array('a' => 'b'))
            ->will($this->returnValue(true));

        $c = new Curl();
        $this->assertTrue($c->setOpt('my opt', 'my val'));
        $this->assertTrue($c->setOptArray(array('a' => 'b')));
    }

    public function testVersion()
    {
        $this->mock
            ->expects($this->at(0))
            ->method('curl_version')
            ->with(CURLVERSION_NOW)
            ->will($this->returnValue('my ver 1'));

        $this->mock
            ->expects($this->at(1))
            ->method('curl_version')
            ->with(42)
            ->will($this->returnValue('my ver 2'));

        $this->assertEquals('my ver 1', Curl::version());
        $this->assertEquals('my ver 2', Curl::version(42));
    }

    public function testExec()
    {
        $this->mock
            ->expects($this->at(0))
            ->method('curl_init')
            ->with(null)
            ->will($this->returnValue('my handle'));

        $this->mock
            ->expects($this->exactly(1))
            ->method('curl_exec')
            ->with('my handle')
            ->will($this->returnValue('my result'));

        $c = new Curl();
        $this->assertEquals('my result', $c->exec());
    }

    public function testExecException()
    {
        $this->mock
            ->expects($this->any())
            ->method('curl_exec')
            ->will($this->returnValue(false));

        $this->mock
            ->expects($this->once())
            ->method('curl_error')
            ->will($this->returnValue('Foo error'));

        $this->mock
            ->expects($this->once())
            ->method('curl_errno')
            ->will($this->returnValue(42));

        $this->mock
            ->expects($this->once())
            ->method('curl_getinfo')
            ->with(null, 0)
            ->will($this->returnValue(array('a' => 'b', 'c' => 4)));

        $c = new Curl();
        try
        {
            $c->exec(23, true);
            $this->fail('No exception');
        }
        catch (\RuntimeException $e)
        {
            $this->assertEquals(42, $e->getCode());
            $this->assertEquals(
                "Foo error after 23 attempt(s). array (\n"
                . "  'a' => 'b',\n"
                . "  'c' => 4,\n"
                . ")",
                $e->getMessage());
        }
    }

    /**
     * @expectedException \InvalidArgumentException
     * @expectedExceptionMessage Attempts count (-2) is not positive
     */
    public function testExecInvalidParam()
    {
        $c = new Curl();
        $c->exec(-2);
    }

    public function testExecReconnecting()
    {
        $this->mock
            ->expects($this->at(0))
            ->method('curl_init')
            ->with(null)
            ->will($this->returnValue('my handle'));

        $this->mock
            ->expects($this->exactly(5))
            ->method('curl_exec')
            ->with('my handle')
            ->will($this->onConsecutiveCalls(false, false, false, false, 'my result'));

        $c = new Curl();
        $this->assertFalse($c->exec(3));
        $this->assertEquals('my result', $c->exec(3));
    }
}
